在 Cesium 三维可视化开发中,洋流(海流)的可视化是海洋相关应用的重要组成部分。然而,有时我们需要将洋流的显示限制在特定的地理区域内,比如某个海域或湾区,而不是整个海洋。本文将介绍如何通过多边形边界结合点在多边形内的判断算法,实现 Cesium 中洋流的区域限制显示。 在海洋可视化场景中,我们通常会加载全球或区域的洋流数据,但实际应用中可能只关注特定区域的洋流情况。如果不做区域限制,会出现以下问题:
数据计算量大,影响性能
视觉上不够聚焦,难以凸显重点区域
洋流可能显示在陆地或不相关的水域上
因此,我们需要一种方法,将洋流的显示限制在指定的多边形区域内
技术方案 我们的解决方案基于以下核心思路:
定义一个多边形区域作为洋流显示的边界
使用点在多边形内的判断算法(射线算法)
在洋流粒子渲染前进行位置判断,仅渲染位于多边形内的粒子
这种方法既保证了视觉效果,又优化了性能。
核心算法:点在多边形内的判断 判断一个点是否在多边形内是计算几何中的经典问题。我们采用的是射线算法(Ray Casting Algorithm),也称为奇偶规则(Even-Odd Rule)。该算法的思路是:
从判断点向任意固定方向(通常选择水平向右)引一条射线
计算这条射线与多边形边界的交点数量
如果交点数为奇数,则点在多边形内;如果为偶数,则点在多边形外
下面是这个算法的详细实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function isPointInPolygon (point, polygon ) { let inside = false ; let x = point[0 ], y = point[1 ]; for (let i = 0 , j = polygon.length - 1 ; i < polygon.length ; j = i++) { let xi = polygon[i][0 ], yi = polygon[i][1 ]; let xj = polygon[j][0 ], yj = polygon[j][1 ]; let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; }
算法原理解析 这个算法看起来很简洁,但蕴含了精妙的几何原理:
射线法的核心思想 :从点发出一条水平射线,统计它与多边形边界的交点数。奇数交点表示在内部,偶数交点表示在外部。
判断边是否跨过射线 :yi > y !== yj > y 这个条件确保了当前边的一个端点在射线上方,另一个在射线下方,即这条边跨过了水平射线。
计算交点位置 :x < ((xj - xi) * (y - yi)) / (yj - yi) + xi 这个表达式计算了边与射线的交点的 x 坐标,然后判断这个交点是否在原点的右侧。
奇偶切换 :每找到一个交点,就翻转 inside 的状态,最终得到点的内外状态。
在 Cesium 中应用 在 Cesium 中应用这个算法,我们需要解决两个问题:
如何获取屏幕坐标对应的地理坐标
如何结合洋流数据与多边形区域判断
获取地理坐标 通过 Cesium 提供的 API,我们可以将屏幕坐标转换为地球表面的经纬度坐标:
1 2 3 4 5 6 7 8 9 10 const cartesian = viewer.camera .pickEllipsoid ( new Cesium .Cartesian2 (x, y), viewer.scene .globe .ellipsoid ); const cartographic = Cesium .Cartographic .fromCartesian (cartesian);const lon = Cesium .Math .toDegrees (cartographic.longitude );const lat = Cesium .Math .toDegrees (cartographic.latitude );
这段代码的作用是将屏幕上的 2D 坐标点(x, y)转换为地球表面上的 3D 坐标点,然后获取其经纬度值。
结合洋流数据与区域判断 最关键的部分是将洋流数据与多边形区域判断结合起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function fieldisDefined (x, y ) { const cartesian = _oceanmap.viewer .camera .pickEllipsoid ( new Cesium .Cartesian2 (x, y), _oceanmap.viewer .scene .globe .ellipsoid ); if (!cartesian) return false ; const cartographic = Cesium .Cartographic .fromCartesian (cartesian); const lon = Cesium .Math .toDegrees (cartographic.longitude ); const lat = Cesium .Math .toDegrees (cartographic.latitude ); const isInOcean = field (x, y)[2 ] !== null ; const isInBoundary = isPointInPolygon ([lon, lat], _oceanmap.bounds ); return isInOcean && isInBoundary; }
这个函数完成了两项重要检查:
确保点位于海洋中(通过洋流数据判断)
确保点位于我们指定的多边形区域内
只有同时满足这两个条件,我们才会在该位置渲染洋流粒子。
实施效果 通过这种方法,我们可以将洋流的显示精确限制在我们感兴趣的区域内,如南海特定海域、台湾海峡或者某个港湾等。这不仅提升了视觉效果,还优化了性能,因为只有部分粒子需要计算和渲染。
完整代码实现 下面是创建带区域限制的洋流场的完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 field = earthCtrl.factory .createOceanField ({ name : "ocean1" , type : "ocean" , upath : "./data/MIT.0000000001_UVEL_1.dat" , vpath : "./data/MIT.0000000001_VVEL_1.dat" , bounds : [ [112.27127810594834 , 19.225753080468763 ], [110.03847818240308 , 17.427581061438815 ], [111.95290379190146 , 12.594412699775589 ], [118.57050910216816 , 13.584956070591218 ], [118.77371765142777 , 19.065063097417365 ], [112.27127810594834 , 19.225753080468763 ], ], }); function isPointInPolygon (point, polygon ) { let inside = false ; let x = point[0 ], y = point[1 ]; for (let i = 0 , j = polygon.length - 1 ; i < polygon.length ; j = i++) { let xi = polygon[i][0 ], yi = polygon[i][1 ]; let xj = polygon[j][0 ], yj = polygon[j][1 ]; let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; } function fieldisDefined (x, y ) { const cartesian = _oceanmap.viewer .camera .pickEllipsoid ( new Cesium .Cartesian2 (x, y), _oceanmap.viewer .scene .globe .ellipsoid ); if (!cartesian) return false ; const cartographic = Cesium .Cartographic .fromCartesian (cartesian); const lon = Cesium .Math .toDegrees (cartographic.longitude ); const lat = Cesium .Math .toDegrees (cartographic.latitude ); const isInOcean = field (x, y)[2 ] !== null ; const isInBoundary = isPointInPolygon ([lon, lat], _oceanmap.bounds ); return isInOcean && isInBoundary; }
总结 通过结合点在多边形内的判断算法与 Cesium 的坐标转换功能,我们成功实现了洋流的区域限制显示。这种方法既保证了视觉效果的聚焦,又优化了性能。在实际应用中,可以根据需要调整多边形边界,灵活控制洋流的显示区域。